#include<iostream>
#include<algorithm>
#include<cmath>
#include"pop.h"

using namespace std;


// Pop class de/constructors

Pop::Pop()
{
  size = 0;
  chromo_size = 0;
  mean_fit = 0.0;
  ffunc_p = NULL;
}

Pop::Pop(int pop_size, int chromo_length, const FFBase &ffunc)
{
  size = pop_size;
  chromo_size = chromo_length;
  mean_fit = 0.0;
  ffunc_p = &ffunc;

  chromos.reserve(int(pop_size*RESERVE_RATIO));

  chromos.assign(pop_size, Chromo());
  int i;
  for (i = 0; i < pop_size; i++)
    chromos[i].re_setup(chromo_size);
  update();
}

Pop::Pop(const Pop &copy)
{
  chromos = copy.chromos;
  mean_fit = copy.mean_fit;
  chromo_size = copy.chromo_size;
  size = copy.size;
  ffunc_p = copy.ffunc_p;
}

Pop::~Pop()
{
  //chromos.~vector<Chromo>();
}

Pop& Pop::operator=(const Pop &copy)
{
  if (this == &copy)
    return *this;

  chromos = copy.chromos;
  size = copy.size;
  chromo_size = copy.chromo_size;
  mean_fit = copy.mean_fit;
  ffunc_p = copy.ffunc_p;
}

double Pop::mean_fitness(double rate)
{
  if (size == 0)
    return 0.0;

  int i;
  double result = 0.0;

  int range = (int)(fabs(rate)*size);

  if (0.0 < rate <= 1.0)
    for (i = 0; i < range; i++)
      result += chromos[i].fitness();

  else if (-1.0 <= rate < 0.0)
    for(i = 0; i < range; i++)
      result += chromos[size - i - 1].fitness();

  else
    return -1.0;

  return result/range;
}

double Pop::a_mean_fitness(double cr_value)
{
  double result = 0.0;
  int counter = 0;

  while(chromos[counter].fitness() > cr_value)
    {
      result += chromos[counter].fitness();
      counter++;
    }

  return result/counter;

}

double Pop::fitness_stdev(double rate)
{
  if (size == 0)
    return 0.0;

  int i;
  double result = 0.0;

  int range = (int)(fabs(rate)*size);

  if (0.0 < rate <= 1.0)
    for (i = 0; i < range; i++)
      result += chromos[i].fitness()*chromos[i].fitness();

  else if (-1.0 <= rate < 0.0)
    for(i = 0; i < range; i++)
      result += chromos[size - i - 1].fitness()*chromos[size - i - 1].fitness();

  else
    return -1.0;

  return sqrt(result/range - mean_fitness(rate));
}

double Pop::a_fitness_stdev(double cr_value)
{
  int range = 0;
  while(chromos[range].fitness() > cr_value)
    {
      range++;
    }
  double rate = (double)(range + 1)/size;
  return fitness_stdev(rate);
}

void Pop::print_chromo(int chromo)
{
  cout << chromos[chromo] << endl;
}

int Pop::col_similarity(int col)
{
  if (col > chromo_size || col < 0)
    return -1;
  
  int result = 0;
  int i;
  for (i = 0; i < size; i++)
  {
    if (chromos[i].loci(col))
      result += -1;
    else
      result += 1;
  }
  if ( result < 0)
    return -result;

  return result;
}
    
void Pop::sort()
{
  std::sort(chromos.begin(), chromos.end(), greater<Chromo>());
}


void Pop::add_chromo(int num)
{
  size += num;
  int i;
  for (i = 0; i < num; i++)
    chromos.push_back(Chromo(chromo_size));
  update(); 
}

void Pop::kill_chromo(int num)
{
  if (num > size)
    return;

  chromos.erase(chromos.begin() + size - num , chromos.end());
  size -= num;
}

void Pop::display_info()
{
  cout << mean_fitness() << '\t' << chromos[0].fitness() << '\t' << size << endl;
}

double Pop::cal_fitness(int chromo)
{
  return chromos[chromo].fitness(ffunc_p->ff(chromos[chromo].debin(), chromo_size));
}

void Pop::update()
{
  int i;
  for (i = 0; i < size; i++)
    if (chromos[i].if_modified())
      cal_fitness(i);
  sort();
}

double Pop::pop_fitness()
{
  return chromos[0].fitness();
}

int Pop::total_ham_dist(int chromo)
{
  int i ;
  int result = 0;
  for (i = 0; i < size; i++)
    result += chromos[i].ham_dist(chromos[chromo]);

  return result;
}

double Pop::ft_diversity(double topr)
{
  sort();
  int counter = 0;
  int range = size*topr;
  double sum = 0.0;
  while (counter < topr)
  {
    double temp_fitness = cal_fitness(counter);
    sum += temp_fitness*temp_fitness;
    counter++;
  }
  double stdev = sqrt((sum - range*mean_fitness()*mean_fitness())/range);
  return stdev;
}


double Pop::hd_diversity(double topr)
{
  sort();
  int counter = 0;
  int range = size*topr;
  double sum1 = 0.0;
  double sum2 = 0.0;
  while (counter < topr)
  {
    double temp_hd = total_ham_dist(counter);
    sum1 += temp_hd;
    sum2 += temp_hd*temp_hd;
    counter++;
  }
  double mean_hd = sum1/range;
  double stdev = sqrt((sum2 - range*mean_hd*mean_hd)/range);
  return stdev;
}


int Pop::pop_size()
{
  return size;
}

void SimpPop::evolve()
{
  int i;
  for (i = 0; i < size; i++)
    chromos[i].rand_mutate(double(i + 1 )/(size + 1));
  update();
}

